import { camelize } from '@vue/shared';
import { isClient } from '@vueuse/core';
import { isFunction, kebabCase } from 'lodash-es';
import { type CSSProperties, type Slots } from 'vue';
import { addUnit } from './dataDeal';
import { isDef, isObject } from './is';

const namespace = 'vc';

/**
 * @description 阻止事件传播
 * @author wfd
 * @date 2022/7/6 11:21
 * @example
 * @param event
 */
export const stopPropagation = (event: Event) => event.stopPropagation();

/**
 * @description preventDefault
 * @author wfd
 * @date 2022/7/6 11:26
 * @example
 * @param event
 * @param isStopPropagation
 */
export function preventDefault (event: Event, isStopPropagation?: boolean) {
  /* istanbul ignore else */
  if (typeof event.cancelable !== 'boolean' || event.cancelable) {
    event.preventDefault();
  }
  
  if (isStopPropagation) {
    stopPropagation(event);
  }
}

/**
 * @description 判断元素是否含有某个 class 名称
 * @author wfd
 * @date 2021/9/19 09:13
 * @param el 元素
 * @param cls class 名称
 */
export function hasClass (el: Element, cls: string) {
  if (!el || !cls) return false;
  if (cls.indexOf(' ') !== -1) throw new Error('className should not contain space.');
  if (el.classList) {
    return el.classList.contains(cls);
  } else {
    return (' ' + el.className + ' ').indexOf(' ' + cls + ' ') > -1;
  }
}

/**
 * @description 去除左右两边空格
 * @author wfd
 * @date 2021/10/5 09:55
 * @param string 要去除的字符串
 */
function trim (string: string) {
  return (string || '').replace(/^[\s\uFEFF]+|[\s\uFEFF]+$/g, '');
}

/**
 * @description 给元素添加 class
 * @author wfd
 * @date 2021/9/19 09:24
 * @param el 传入的 element 元素
 * @param cls 传入的类名，多个类名用空格分割
 */
export function addClass (el: Element, cls: string) {
  if (!el) return;
  let curClass = el.className;
  const classes = (cls || '').split(' ');
  
  for (let i = 0, j = classes.length; i < j; i++) {
    const clsName = classes[i];
    if (!clsName) continue;
    
    if (el.classList) {
      el.classList.add(clsName);
    } else if (!hasClass(el, clsName)) {
      curClass += ' ' + clsName;
    }
  }
  if (!el.classList) {
    el.className = curClass;
  }
}

/**
 * @description 移除元素的 class 属性
 * @author wfd
 * @date 2021/9/19 09:36
 * @param el
 * @param cls
 */
export function removeClass (el: Element, cls: string) {
  if (!el || !cls) return;
  const classes = cls.split(' ');
  let curClass = ' ' + el.className + ' ';
  
  for (let i = 0, j = classes.length; i < j; i++) {
    const clsName = classes[i];
    if (!clsName) continue;
    
    if (el.classList) {
      el.classList.remove(clsName);
    } else if (hasClass(el, clsName)) {
      curClass = curClass.replace(' ' + clsName + ' ', ' ');
    }
  }
  if (!el.classList) {
    el.className = trim(curClass);
  }
}

export function getSlot (slots: Slots, slot = 'default', data?: any) {
  if (!slots || !Reflect.has(slots, slot)) {
    return null;
  }
  if (!isFunction(slots[slot])) {
    console.error(`${slot} is not a function!`);
    return null;
  }
  const slotFn = slots[slot];
  if (!slotFn) return null;
  return slotFn(data);
}

/**
 * extends slots
 * @param slots
 * @param excludeKeys
 */
export function extendSlots (slots: Slots, excludeKeys: string[] = []) {
  const slotKeys = Object.keys(slots);
  const ret: any = {};
  slotKeys.map((key) => {
    if (excludeKeys.includes(key)) {
      return null;
    }
    ret[key] = () => getSlot(slots, key);
  });
  return ret;
}

export const getStyle = (element: HTMLElement, styleName: keyof CSSProperties): string => {
  if (!isClient || !element || !styleName) return '';
  
  let key = camelize(styleName);
  if (key === 'float') key = 'cssFloat';
  try {
    const style = (element.style as any)[key];
    if (style) return style;
    const computed: any = document.defaultView?.getComputedStyle(element, '');
    return computed ? computed[key] : '';
  } catch {
    return (element.style as any)[key];
  }
};

type CssPropertiesType = CSSProperties | keyof CSSProperties;

export const setStyle = (element: HTMLElement, styleName: CssPropertiesType,
  value?: string | number) => {
  if (!element || !styleName) return;
  
  if (isObject(styleName)) {
    // @ts-ignore
    Object.entries(styleName).forEach(
      ([prop, value]) => setStyle(element, prop as CssPropertiesType, value));
  } else {
    const key: any = camelize(styleName);
    element.style[key] = value as any;
  }
};

export const removeStyle = (element: HTMLElement, style: CssPropertiesType) => {
  if (!element || !style) return;
  
  if (isObject(style)) {
    Object.keys(style).forEach((prop) => removeStyle(element, prop as CssPropertiesType));
  } else {
    setStyle(element, style, '');
  }
};

export function getZIndexStyle (zIndex?: Numberish) {
  const style: CSSProperties = {};
  if (zIndex !== undefined) {
    style.zIndex = +zIndex;
  }
  return style;
}

/** empty function */
export function noop () {}

export function getSizeStyle (originSize?: Numberish | Numberish[]): CSSProperties | undefined {
  if (isDef(originSize)) {
    if (Array.isArray(originSize)) {
      return {
        width: addUnit(originSize[0]),
        height: addUnit(originSize[1])
      };
    }
    const size = addUnit(originSize);
    return {
      width: size,
      height: size
    };
  }
}

/**
 * @description 将驼峰形式的对象转化为 css var 变量
 * @author wfd
 * @date 2022/9/21 15:05
 * @example
 * @param {Record<string, Numberish>} vars
 * @param name
 * @returns {Record<string, Numberish>}
 */
export function generateCssVars(vars: Record<string, Numberish | undefined>, name?) {
  const cssVars: Record<string, Numberish | undefined> = {};
  Object.keys(vars).forEach((key) => {
    cssVars[`--${namespace}-${name ? name + '-' : ''}${kebabCase(key)}`] = vars[key];
  });
  return cssVars;
}

/**
 * Trigger an event
 * mouseenter, mouseleave, mouseover, keyup, change, click, etc.
 * @param  {HTMLElement} elm
 * @param  {String} name
 * @param  {*} opts
 */
export const triggerEvent = function (elm: HTMLElement, name: string,
  ...opts: Array<boolean>): HTMLElement {
  let eventName: string;
  
  if (name.includes('mouse') || name.includes('click')) {
    eventName = 'MouseEvents';
  } else if (name.includes('key')) {
    eventName = 'KeyboardEvent';
  } else {
    eventName = 'HTMLEvents';
  }
  const evt = document.createEvent(eventName);
  
  evt.initEvent(name, ...opts);
  elm.dispatchEvent(evt);
  return elm;
};

/**
 * @description 简单添加千分位,调用 Number.prototype.toLocalString() 方法
 * @param { string | number } value
 * @param {string} replaceEmpty 代替空值的占位符 default : '-'
 * @param { 1|2|3|4 } fixed 保留小数位数
 * @returns { string }
 */
export const formatterDigit = (value: string | number, fixed?: 0 | 1 | 2 | 3 | 4,
  replaceEmpty?: string) => {
  if (isNaN(Number(`${value}`))) {
    return replaceEmpty ?? '-';
  } else {
    return Number(value).toLocaleString('zh', { minimumFractionDigits: fixed ?? 0, maximumFractionDigits: fixed ?? 0 });
  }
};

export const isInContainer = (el?: Element, container?: Element | Window): boolean => {
  if (!isClient || !el || !container) return false;
  
  const elRect = el.getBoundingClientRect();
  
  let containerRect: Pick<DOMRect, 'top' | 'bottom' | 'left' | 'right'>;
  if (container instanceof Element) {
    containerRect = container.getBoundingClientRect();
  } else {
    containerRect = {
      top: 0,
      right: window.innerWidth,
      bottom: window.innerHeight,
      left: 0
    };
  }
  return elRect.top < containerRect.bottom && elRect.bottom > containerRect.top && elRect.right > containerRect.left && elRect.left < containerRect.right;
};

export const isScroll = (el: HTMLElement, isVertical?: boolean): boolean => {
  if (!isClient) return false;
  
  const key = (
    {
      undefined: 'overflow',
      true: 'overflow-y',
      false: 'overflow-x'
    } as const
  )[String(isVertical)]!;
  const overflow = getStyle(el, key);
  return ['scroll', 'auto', 'overlay'].some((s) => overflow.includes(s));
};

export const getScrollContainer = (el: HTMLElement,
  isVertical?: boolean): Window | HTMLElement | undefined => {
  if (!isClient) return;
  
  let parent: HTMLElement = el;
  while (parent) {
    if ([window, document, document.documentElement].includes(parent)) return window;
    
    if (isScroll(parent, isVertical)) return parent;
    
    parent = parent.parentNode as HTMLElement;
  }
  
  return parent;
};

/** 加载 css link */
export function loadStyle (url) {
  const link = document.createElement('link');
  link.type = 'text/css';
  link.rel = 'stylesheet';
  link.href = url;
  const head = document.getElementsByTagName('head')[0];
  head.appendChild(link);
}

/** 元素跳转 */
export function jumpElement(id, options = { behavior: 'smooth', block: 'start' } as ScrollIntoViewOptions) {
  (document.getElementById(id) as HTMLElement)?.scrollIntoView(options);
}
